<!-- 操作列组件 -->
<template>
  <el-table-column>
    <template #header>
      <div style="display: flex; align-items: center">
        <span>操作</span>
        <el-icon v-if="Object.keys(rowData).length" :size="19" color="#409eff" @click="addMain">
          <CirclePlusFilled />
        </el-icon>
      </div>
    </template>
    <template #default="{ $index, row }">
      <slot name="before" :row="row" :$index="$index" />
      <el-link
        :underline="false"
        v-show="!row.isEdit && layout.includes('edit') && custOperHandle(row)['edit']"
        type="primary"
        class="font-13"
        @click="editMain(row, $index)"
      >
        编辑
      </el-link>
      <el-link
        :underline="false"
        v-show="row.isEdit && layout.includes('save')"
        type="success"
        class="font-13"
        @click="saveMain(row, $index)"
      >
        保存
      </el-link>
      <el-link
        :underline="false"
        v-show="row.isEdit && row[primaryKey] && layout.includes('cancel')"
        type="info"
        class="mr-l10 font-13"
        @click="cancelMain(row)"
      >
        取消
      </el-link>
      <el-link
        :underline="false"
        v-show="
          (!row.isEdit || !row[primaryKey]) &&
          layout.includes('delete') &&
          custOperHandle(row)['delete']
        "
        type="danger"
        class="mr-l10 font-13"
        @click="deleteMain(row, $index)"
      >
        删除
      </el-link>
      <slot name="after" :row="row" :$index="$index" />
    </template>
  </el-table-column>
</template>

<script lang="ts" setup name="LtTableColumnOperation">
  import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'
  import { computed, reactive, nextTick, getCurrentInstance, onMounted, ref, PropType } from 'vue'
  import { ValidateInfo, ValidateReg, ValidateRange } from './index.d'
  import { throttle } from './util'
  import * as API from '@/api/columnEditApi/columnEditApi'

  function isValidKey(key: string | number | symbol, object: object): key is keyof typeof object {
    return key in object
  }

  // props的接收
  const props = defineProps({
    // 表格的数据
    modelValue: {
      type: Array
    },
    // 是否自定义编辑
    customEdit: {
      type: Boolean,
      default: false
    },
    // 是否添加时为头部插入
    unshift: {
      type: Boolean,
      default: true
    },
    // 行数据的标准格式
    rowData: {
      type: Object,
      default: () => ({})
    },
    // 布局
    layout: {
      type: Array,
      default: () => ['save', 'delete', 'edit', 'cancel']
    },
    // 删除操作对应的api
    deleteApi: {
      type: String,
      default: ''
    },
    // 数据的主键
    primaryKey: {
      type: String,
      default: 'id'
    },
    // 需要判断空的字段
    validateProp: {
      type: [Array, String],
      default: () => []
    },
    // 正则校验
    validateReg: {
      type: Array as PropType<ValidateReg[]>,
      default: () => []
    },
    // 范围校验
    validateRange: {
      type: Array as PropType<ValidateRange[]>,
      default: () => []
    },
    // 内置保存操作对应的api
    saveApi: {
      type: String,
      default: ''
    },
    // 内置编辑操作对应的api
    editApi: {
      type: String,
      default: ''
    },
    // 自定义校验
    custValidate: {
      type: Function,
      default: () => () => {}
    },
    // 是否可以无限添加
    unlimitedAdd: {
      type: Boolean,
      default: false
    },
    // 自定义操作列的显示
    custOperDisplay: {
      type: Function,
      default: () => () => false
    },
    // 添加时的前置钩子函数
    addBefore: {
      type: Function
    },
    // 添加或者编辑需要自动聚焦的列
    focusColumn: {
      type: String,
      default: ''
    },
    validateRepeat: {
      type: [Array, String],
      default: () => []
    }
  })

  // 整个组件emit
  const emit = defineEmits([
    'update:modelValue',
    'edit-confirm',
    'request-before',
    'save-confirm',
    'save',
    'delete-confirm',
    'delete'
  ])

  // 使用计算属性的get,set来实现对于v-model数据的操作
  const tableData: any = computed({
    get() {
      return props.modelValue
    },
    set() {}
  })

  // 行数据的备份,用于后续的还原,key为数据主键,value为行数据
  const rowMapCopy: any = reactive({})
  const parentUid = ref<number | undefined>(undefined)

  /**
   * 新增
   */
  const addMain = () => {
    // 判断是否允许无限新增
    if (!props.unlimitedAdd) {
      for (let i = 0; i < tableData.value.length; i++) {
        if (!tableData.value[i][props.primaryKey]) {
          ElMessage({ message: `请先保存第${i + 1}条数据` })
          return
        }
      }
    }

    // 对rowData追加isEdit字段
    const row = { ...props.rowData }
    row.isEdit = false

    if (props.addBefore && typeof props.addBefore === 'function') props.addBefore(row)

    // 追加至表格数组中
    props.unshift ? tableData.value.unshift(row) : tableData.value.push(row)
    const activeIndex = props.unshift ? 0 : tableData.value.length - 1

    editMain(tableData.value[activeIndex], activeIndex)
  }

  /**
   * 编辑
   * @param {Object} row      当前行数据
   * @param {Object} index    当前行索引
   */
  const editMain = (row: any, index: number) => {
    getParentUid()
    row.isEdit = true
    // 保存一份数据,用于取消时的数据还原
    if (row[props.primaryKey]) {
      rowMapCopy[row[props.primaryKey]] = JSON.parse(JSON.stringify(row))
    }
    // 自动聚焦
    autoFocus(index)
    nextTick(() => {
      emit('edit-confirm', {
        row,
        index
      })
    })
  }

  /**
   * 取消
   * @param {Object} row      当前行数据
   */
  const cancelMain = async (row: any) => {
    let flag = false
    // 当前行数据
    const activeRow = JSON.parse(JSON.stringify(row))

    const rowCopy = JSON.parse(JSON.stringify(rowMapCopy[row[props.primaryKey]]))

    // // 1.先剔除可能存在的排序字段
    // if ('sortField' in this.$parent.$parent) {
    //   delete activeRow[this.$parent.$parent.sortField]
    //   delete rowCopy[this.$parent.$parent.sortField]
    // }

    // 2.再判断是否进行了更改
    if (JSON.stringify(activeRow) !== JSON.stringify(rowCopy)) {
      await ElMessageBox.confirm('此操作将对数据进行还原,您确定吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      })
        .then(() => {
          // 数据对比
          for (const key in row) {
            if (key !== 'isEdit' && row[key] !== rowCopy[key]) {
              // 数据还原
              row[key] = rowCopy[key]
            }
          }
          row.isEdit = !row.isEdit
          flag = true
        })
        .catch(() => {
          flag = true
        })
    }
    if (flag) return
    row.isEdit = !row.isEdit
  }

  /**
   * 删除
   * @param {Object} row      当前行数据
   * @param {Object} index    当前行索引
   */
  const deleteMain = throttle((row: any, index: number) => {
    // 前端删除
    if (!row[props.primaryKey]) {
      tableData.value.splice(index, 1)
      ElNotification.success({ message: '删除成功' })
      return
    }
    ElMessageBox.confirm('是否删除这条记录?', '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    })
      .then(() => {
        if (props.deleteApi && isValidKey(props.deleteApi, API)) {
          const deleteApi: any = API[props.deleteApi]

          deleteApi({
            [props.primaryKey]: row[props.primaryKey]
          })
            .then(() => {
              ElNotification.success({ message: '删除成功' })
              tableData.value.splice(index, 1)
              emit('delete-confirm', true)
            })
            .catch(() => {
              ElNotification.error({ message: '删除失败' })
              emit('delete-confirm', false)
            })
        } else {
          emit('delete', {
            row,
            index
          })
        }
      })
      .catch(() => {})
  }, 1000)

  /**
   * 保存
   * @param {Object} row      当前行数据
   * @param {Object} index    当前行索引
   */
  const saveMain = (row: any, index: number) => {
    const result: ValidateInfo = validateColmn(row, index)
    if (!result.valid) {
      ElMessage({ message: result.error })
      return
    }
    // 自定义校验
    if (!props.custValidate(row)) return

    // 内置的保存
    if (props.saveApi || props.editApi) {
      const api =
        props.saveApi && props.editApi
          ? row[props.primaryKey]
            ? props.editApi
            : props.saveApi
          : props.saveApi

      emit('request-before', {
        row,
        index
      })
      const temp: any = isValidKey(api, API) ? API[api] : ''
      temp(row)
        .then((res: any) => {
          ElNotification.success({ message: '保存成功' })
          emit('save-confirm', true)
          console.log(res)
          // 主键更新,用于后续的编辑
          if (typeof res === 'object' && res[props.primaryKey]) {
            row[props.primaryKey] = res[props.primaryKey]
          }

          row.isEdit = false
        })
        .catch(() => {
          ElNotification.error({ message: '保存失败' })
          emit('save-confirm', false)
        })
    }
    // 自定义保存操作
    emit('save', {
      row,
      index
    })
  }

  /**
   * 校验函数
   */
  const validateColmn = (row: any, index: number) => {
    const validate: ValidateInfo = {
      error: '',
      valid: false
    }
    console.log(123)
    // 1.1非空校验
    if (props.validateProp.length) {
      const validatePropList: string[] | any =
        typeof props.validateProp === 'string'
          ? props.validateProp.replace(/\s+/g, '').split(',')
          : props.validateProp

      const i = validatePropList.findIndex(
        (item: string) =>
          item in row && (row[item] === '' || row[item] === null || row[item] === undefined)
      )
      if (i !== -1) {
        validate.error = '数据不能为空,请填写'
        autoFocus(index, validatePropList[i])
        return validate
      }
    }
    // 1.2正则校验字段
    if (props.validateReg.length) {
      const i = props.validateReg.findIndex((item: ValidateReg) => !item.reg.test(row[item.prop]))

      if (i !== -1) {
        const obj: ValidateReg = props.validateReg[i]

        validate.error = obj.msg || '数据格式不正确,请检查'
        autoFocus(index, obj.prop)
        return validate
      }
    }

    // 1.3校验范围
    if (props.validateRange.length) {
      interface ValidateRange {
        prop: string
        min?: number
        max?: number
        msg?: string
      }

      // 范围校验
      const rangeCheck = (item: ValidateRange) => {
        /**
         * 问题: ts会认为 item['min']可能会为undefind且参与了运算,可能会出问题,所以会提示错误
         * 我们就可以使用断言来告诉 TS：相信我，我知道自己在干什么
         * 解决方法: 使用断言或者非空断言
         * 1. item['min'] as number
         * 2. <number>item['min']
         * 3. item['min']!  // 告诉item['min']一定有值
         */
        if ('min' in item && 'max' in item)
          return row[item.prop] < (item.min as number) || row[item.prop] > (item.max as number)
        if ('min' in item) return row[item.prop] < (item.min as number)
        if ('max' in item) return row[item.prop] > (item.max as number)
        return false
      }

      // 范围校验提示
      const rangeMessage = (item: ValidateRange) => {
        if ('min' in item && 'max' in item) return `所限制的范围为${item.min}至${item.max}`
        if ('min' in item) return `所限制的最小值为${item.min}`
        if ('max' in item) return `所限制的最大值为${item.max}`
        return ''
      }

      const i = props.validateRange.findIndex(item => rangeCheck(item))
      if (i !== -1) {
        const obj: ValidateRange = props.validateRange[i]
        validate.error = obj.msg || rangeMessage(obj)
        autoFocus(index, obj.prop)
        return validate
      }
    }
    // 1.4重复性校验
    if (props.validateRepeat.length) {
      const validateRepeatList: string[] | any =
        typeof props.validateRepeat === 'string'
          ? props.validateRepeat.replace(/\s+/g, '').split(',')
          : props.validateRepeat
      const i = validateRepeatList.findIndex((item: string) => {
        // 遍历表格数据,判断其他数据是否有一样的
        for (let j = 0; j < tableData.value.length; j++) {
          if (tableData.value[j][item] === row[item] && j !== index) {
            return true
          }
        }
        return false
      })

      if (i !== -1) {
        validate.error = `数据不能出现重复项,请检查`
        autoFocus(index, validateRepeatList[i].prop)
        return validate
      }
    }
    validate.valid = true
    return validate
  }

  /**
   * 自动聚焦
   * @param {Object} index  索引
   */
  const autoFocus = (index: number, prop: string = '') => {
    const inputFocus = (index: number, prop: string) => {
      nextTick(() => {
        const element: HTMLInputElement | null = document.querySelector(
          `input[cust-id=${prop}-${index}-${parentUid.value}]`
        )
        // 可能存在自定义编辑插槽,所以此处需要做判断
        if (element) {
          element.focus()
        } else {
          throw `${prop}字段并不是一个有效值,请检查`
        }
      })
    }
    // 内部校验时的指定字段聚焦
    if (prop) {
      inputFocus(index, prop)
      return
    }
    // 指定列进行自动聚焦
    if (props.focusColumn) inputFocus(index, props.focusColumn)
  }

  /**
   * 自定义操作列的处理
   * @param {Object} row
   */
  const custOperHandle = (row: object) => {
    const defalutData = {
      edit: true,
      delete: true
    }

    if (!props.custOperDisplay) return defalutData
    return { ...defalutData, ...props.custOperDisplay(row) }
  }

  const getParentUid = () => {
    const currentExample = getCurrentInstance()
    return currentExample?.parent?.uid
  }

  onMounted(() => {
    parentUid.value = getParentUid()
  })
</script>

<style scoped>
  .font-13 {
    font-size: 13px !important;
  }
  .font-18 {
    font-size: 18px !important;
  }
  .mr-l5 {
    margin-left: 5px !important;
  }
  .pointer {
    cursor: pointer !important;
  }
  .mr-r10 {
    margin-right: 10px !important;
  }
  .mr-l10 {
    margin-left: 10px !important;
  }
  .d-flex {
    display: flex;
  }
  .a-center {
    align-items: center;
  }
  .main-color {
    color: #1890ff;
  }
</style>
